Skip to content

2636. Promise 对象池

2636. Promise 对象池

ts
type F = () => Promise<any>

async function promisePool(functions: F[], n: number): Promise<any[]> {
    // 利用数组自身的迭代器,减少变量的维护
  const iterator = functions[Symbol.iterator]()

    // 任务执行器,存在空闲,主动任务队列拿取新任务执行
  async function worker() {
    while (true) {
      const { value: fn, done } = iterator.next()
      if (done) break
      await fn()
    }
  }

  // 创建 n 个 worker,每个 worker 会连续取任务执行
  const workers = Array.from({ length: n }, () => worker())

  // 等待所有 worker 完成
  return Promise.allSettled(workers)
}
/**
 * const sleep = (t) => new Promise(res => setTimeout(res, t));
 * promisePool([() => sleep(500), () => sleep(400)], 1)
 *   .then(console.log) // After 900ms
 */

设计思路

这里本质上是一个并发控制问题。核心约束是:最多 N 个任务同时执行,但不能直接把所有任务一次性用 Promise.all 扔出去。

实现中用了两个关键设计:

1. 迭代器驱动消费。 functions[Symbol.iterator]() 返回一个迭代器,所有 worker 共享它。由于 iterator.next() 是有状态的(每次调用推进位置),多个 worker 不会拿到同一个任务。

2. worker 模式。 每个 worker 是一个 async function,它不停地从迭代器取任务执行,直到迭代器枯竭。N 个 worker 并发运行,保证同时最多 N 个任务在执行;一个 worker 完成当前任务后,就立即去取下一个。

Promise.allSettled vs Promise.all

这里用 allSettled 而不是 all,原因是:如果某个任务 reject,Promise.all 会立即短路,其他 worker 仍在执行中的结果就拿不到了。allSettled 则会等所有 worker 都结束。

复杂度

调度本身是 O(F),F 为任务数。真正的时间消耗在于任务自身。空间方面,迭代器是 O(1) 的额外开销——没有预分配结果数组,没有中间队列。